Para esta sesión, los datos puedes descargarlos en tu carpeta data desde este enlace.
Usemos de nuevo los datos de los innovadores en Seattle:
Como se ve, tenemos la matriz de adyacencia, la cual abriremos esta vez en R:
# limpiar memoria
rm(list = ls())
# abrir red
adjacency=read.csv(file.path('data','seattleTop_adjMx.csv'),row.names = 1) # la tabla tiene este nombre
matrix <- as.matrix(adjacency)
# abrir atributos
attributes=read.csv(file.path('data','seattleTop_attrTbl.csv')) # la tabla tiene este nombre
En R, es común usar igraph para análisis básicos de redes. Creamos primero la red usando la matriz de adyacencia:
library(igraph)
topsNet <- graph_from_adjacency_matrix(matrix, mode="directed",weighted = TRUE)
summary(topsNet)
## IGRAPH e462899 DNW- 46 588 --
## + attr: name (v/c), weight (e/n)
Como se ve, igraph nos informa que se ha creado la red asignándole un código (irrelevante), que es una red dirigida (DNW, la W indica que ha creado pesos en los enlaces), con 46 nodos y 588 enlaces. Indica que tiene un atributo name para los vértices (nodos) de tipo cáracter (texto), y que los enlaces (edges) tienen un atributo weight de tipo numérico.
Como tenemos una tabla con más atributos para los nodos, podemos añadirlos fácilmente:
V(topsNet)$male <- attributes$male
V(topsNet)$popularity <- attributes$followers
# ahora
summary(topsNet)
## IGRAPH e462899 DNW- 46 588 --
## + attr: name (v/c), male (v/n), popularity (v/n), weight (e/n)
Ya están los nuevos atributos para vértices, popularity y male, ambos de tipo numérico.
Exportemos esta red para que pueda ser leída por Gephi:
igraph::write_graph(topsNet,file.path('data',"topsNetFull.graphml"),
format = "graphml")
Recordemos que nuestra red de Seattletonians:
is_connected(topsNet,mode = c("strong"))
## [1] FALSE
count_components(topsNet, mode = c("strong"))
## [1] 4
g_comp=igraph::components(topsNet, mode = c("strong"))
g_comp
## $membership
## rachelerman mattmcilwain DaveParkerSEA toddbishop ashannstew
## 1 1 1 1 1
## LeslieFeinzaig akipman matt_oppy gilbert juliesandler
## 1 1 1 1 1
## BradSmi crashdev ShaunaCausey john_gabbert moniguzman
## 1 1 1 1 1
## mattmday Rich_Barton daryn lovelletters etzioni
## 1 1 1 1 1
## MissDestructo heatherredman danshapiro medinism KieranSnyder
## 1 1 1 1 1
## hadip RajSinghSeattle funcOfJoe kirbywinfield stevesi
## 1 1 1 1 1
## Ryanintheus sonalpmane SoGulley X2morrowknight jinman
## 1 1 1 3 1
## tarah Jenerationy lanctot Kristen_Hammy nhuntwalker
## 4 1 1 1 1
## eugenio_pace JenMsft PeterHamilton sarahstood mcolacurcio
## 2 1 1 1 1
## marybethlambert
## 1
##
## $csize
## [1] 43 1 1 1
##
## $no
## [1] 4
Guardemos la membresía a esos componentes como un atributo del nodo:
V(topsNet)$component=g_comp$membership
Vemos que uno de los componentes strongly connected es muy numeroso. Para efectos prácticos, quedémonos con ese:
# seleccionando
nodes_Subset=V(topsNet)[V(topsNet)$component==1]
# filtrando red
topsNet_giant=induced_subgraph(topsNet, nodes_Subset)
# ahora:
summary(topsNet_giant)
## IGRAPH 9b4f299 DNW- 43 575 --
## + attr: name (v/c), male (v/n), popularity (v/n), component (v/n),
## | weight (e/n)
Exportemos esta sub red para que pueda ser leída por Gephi:
igraph::write_graph(topsNet_giant,file.path('data',"topsNet_giant.graphml"),
format = "graphml")
Veamos algunos valores importantes:
edge_density(topsNet_giant)
## [1] 0.3183832
farthest_vertices(topsNet_giant)
## $vertices
## + 2/43 vertices, named, from 9b4f299:
## [1] akipman sonalpmane
##
## $distance
## [1] 4
reciprocity(topsNet_giant)
## [1] 0.6782609
¿Qué tanta homofilia hay en la red?
Hacia los más conectados:
assortativity_degree(topsNet_giant,directed = T)
## [1] -0.248864
assortativity(topsNet_giant, values=V(topsNet_giant)$male, directed = T)
## [1] 0.06411198
assortativity(topsNet_giant, values=V(topsNet_giant)$popularity, directed = T)
## [1] -0.01605838
Abramos topsNet_giant.graphml, y a continuación:
Un paso inicial es saber qué tan alto es el coeficiente de clusterización:
transitivity(as.undirected(topsNet_giant,mode='collapse'),type = "localaverage")
## [1] 0.645001
transitivity(as.undirected(topsNet_giant,mode='collapse'),type = "global")
## [1] 0.5666072
Aquí igraph ha implementado el algoritmo sólo para el caso no dirigido. Por lo que probablemente este valor sea mayor al caso dirigido. Con un valor mayor a 0.5, podemos asumir que se puede encontrar clusters.
Con el grafo actual (topsNet_giant.graphml), Calculemos el coeficiente de clusterización en Gephi y comparemos.
Para darle más soporte a nuestra búsqueda de comunidades, hay que explorar la presencia de cliques.El clique es un conjunto de nodos donde todos pueden conectarse con todos. Para este caso, usarameos la librería statnet.
Como nuestra red la creamos con igraph, hay que convertirlo a grafo de statnet:
topsNet_giant_net <- intergraph::asNetwork(topsNet_giant)
# ya es de statnet:
topsNet_giant_net
## Network attributes:
## vertices = 43
## directed = TRUE
## hyper = FALSE
## loops = FALSE
## multiple = FALSE
## bipartite = FALSE
## total edges= 575
## missing edges= 0
## non-missing edges= 575
##
## Vertex attribute names:
## component male popularity vertex.names
##
## Edge attribute names:
## weight
Nuestro propósito aquí es saber si hay cliques:
library(statnet)
census <- clique.census(topsNet_giant_net)
# aqui vemos
census$clique.count
## Agg rachelerman mattmcilwain DaveParkerSEA toddbishop ashannstew
## 1 0 0 0 0 0 0
## 2 10 0 2 0 0 0
## 3 17 2 0 2 2 0
## 4 13 4 2 1 12 0
## 5 9 2 0 0 8 1
## 6 12 6 0 3 8 1
## 7 6 4 0 1 6 2
## 8 2 2 0 2 2 0
## 9 1 1 0 1 1 0
## LeslieFeinzaig akipman matt_oppy gilbert juliesandler BradSmi crashdev
## 1 0 0 0 0 0 0 0
## 2 0 1 0 0 0 2 1
## 3 0 0 1 2 1 1 2
## 4 0 0 0 1 4 2 0
## 5 3 0 0 0 2 0 2
## 6 4 0 3 0 9 0 2
## 7 4 0 1 0 4 0 1
## 8 1 0 1 0 2 0 0
## 9 1 0 0 0 1 0 0
## ShaunaCausey john_gabbert moniguzman mattmday Rich_Barton daryn lovelletters
## 1 0 0 0 0 0 0 0
## 2 0 0 0 1 1 0 0
## 3 3 0 4 0 1 0 0
## 4 5 0 4 2 0 1 0
## 5 2 0 3 2 0 1 3
## 6 3 2 3 0 0 1 8
## 7 3 0 1 0 0 0 5
## 8 2 0 0 0 0 0 2
## 9 1 0 0 0 0 0 1
## etzioni MissDestructo heatherredman danshapiro medinism KieranSnyder hadip
## 1 0 0 0 0 0 0 0
## 2 1 0 0 0 2 1 0
## 3 0 0 2 4 0 2 3
## 4 2 2 0 0 0 2 0
## 5 0 2 5 0 0 1 0
## 6 1 5 5 0 0 1 0
## 7 1 3 4 0 0 0 0
## 8 1 0 1 0 0 0 0
## 9 0 1 1 0 0 0 0
## RajSinghSeattle funcOfJoe kirbywinfield stevesi Ryanintheus sonalpmane
## 1 0 0 0 0 0 0
## 2 2 1 1 1 0 0
## 3 2 0 2 5 2 0
## 4 0 0 0 0 1 0
## 5 0 0 1 3 1 0
## 6 0 0 2 2 1 2
## 7 0 0 0 1 0 0
## 8 0 0 0 0 0 0
## 9 0 0 0 0 0 0
## SoGulley jinman Jenerationy lanctot Kristen_Hammy nhuntwalker JenMsft
## 1 0 0 0 0 0 0 0
## 2 0 0 0 1 0 1 1
## 3 1 0 3 0 0 2 0
## 4 0 1 0 1 1 0 0
## 5 0 0 0 0 2 0 0
## 6 0 0 0 0 0 0 0
## 7 1 0 0 0 0 0 0
## 8 0 0 0 0 0 0 0
## 9 0 0 0 0 0 0 0
## PeterHamilton sarahstood mcolacurcio marybethlambert
## 1 0 0 0 0
## 2 0 0 0 0
## 3 1 0 0 1
## 4 1 2 1 0
## 5 0 0 1 0
## 6 0 0 0 0
## 7 0 0 0 0
## 8 0 0 0 0
## 9 0 0 0 0
La cantidad de filas nos dice los tamaños de los cliques máximales, es decir, un clique que no se puede ampliar incluyendo un nodo adyacente más (no es un subconjunto de un camarilla más grande):
census$clique.count[1,1]
## [1] 0
census$clique.count[2,1]
## [1] 10
census$clique.count[9,1]
## [1] 1
Si hay cliques, se sospecha la presencia de comunidades.
sort(edge_betweenness(topsNet_giant,directed = T),decreasing = T)[1:5]
## [1] 42.12500 41.80284 26.52518 26.29048 23.73203
l=layout_with_fr(topsNet_giant)
plot(topsNet_giant,layout=l,edge.arrow.size=1,vertex.label='',
edge.color=ifelse(edge_betweenness(topsNet_giant)>40,'red','grey90'))
El trabajo sería ir eliminando vertices hasta quedarse con la ‘mejor’ partición. Esto lo hace directamente el algoritmo the Girvan-Newman:
topsNet_giant_GN <- cluster_edge_betweenness(topsNet_giant, directed=T)
topsNet_giant_GN
## IGRAPH clustering edge betweenness, groups: 2, mod: 0.0032
## + groups:
## $`1`
## [1] "rachelerman" "mattmcilwain" "DaveParkerSEA" "toddbishop"
## [5] "ashannstew" "LeslieFeinzaig" "matt_oppy" "gilbert"
## [9] "juliesandler" "BradSmi" "crashdev" "ShaunaCausey"
## [13] "john_gabbert" "moniguzman" "mattmday" "Rich_Barton"
## [17] "daryn" "lovelletters" "etzioni" "MissDestructo"
## [21] "heatherredman" "danshapiro" "medinism" "KieranSnyder"
## [25] "hadip" "RajSinghSeattle" "funcOfJoe" "kirbywinfield"
## [29] "stevesi" "Ryanintheus" "sonalpmane" "SoGulley"
## [33] "jinman" "Jenerationy" "lanctot" "Kristen_Hammy"
## + ... omitted several groups/vertices
Se indica que ha encontrado sólo dos comunidades. Nótese que indica que este resultado es el que dió la ’mejor” partición: la mejor modularidad:
modularity(topsNet_giant_GN)
## [1] 0.003175803
El detalle de las dos comunidades lo vemos así:
communities(topsNet_giant_GN)
## $`1`
## [1] "rachelerman" "mattmcilwain" "DaveParkerSEA" "toddbishop"
## [5] "ashannstew" "LeslieFeinzaig" "matt_oppy" "gilbert"
## [9] "juliesandler" "BradSmi" "crashdev" "ShaunaCausey"
## [13] "john_gabbert" "moniguzman" "mattmday" "Rich_Barton"
## [17] "daryn" "lovelletters" "etzioni" "MissDestructo"
## [21] "heatherredman" "danshapiro" "medinism" "KieranSnyder"
## [25] "hadip" "RajSinghSeattle" "funcOfJoe" "kirbywinfield"
## [29] "stevesi" "Ryanintheus" "sonalpmane" "SoGulley"
## [33] "jinman" "Jenerationy" "lanctot" "Kristen_Hammy"
## [37] "nhuntwalker" "PeterHamilton" "sarahstood" "mcolacurcio"
## [41] "marybethlambert"
##
## $`2`
## [1] "akipman" "JenMsft"
La pertenecia a cada grupo podemos guardarla como un atributo:
V(topsNet_giant)$GNpartition=topsNet_giant_GN$membership
Veamos el resultado de manera gráfica:
l=layout_with_kk(topsNet_giant)
plot(topsNet_giant_GN,topsNet_giant,rescale=T, layout=l,vertex.label='',edge.arrow.size=.2)
topsNet_giant_LV=cluster_louvain(as.undirected(topsNet_giant,mode="mutual"))
topsNet_giant_LV
## IGRAPH clustering multi level, groups: 4, mod: 0.18
## + groups:
## $`1`
## [1] "rachelerman" "gilbert" "mattmday" "daryn" "danshapiro"
## [6] "medinism" "KieranSnyder" "stevesi" "JenMsft"
##
## $`2`
## [1] "mattmcilwain" "toddbishop" "akipman" "matt_oppy"
## [5] "juliesandler" "BradSmi" "crashdev" "john_gabbert"
## [9] "Rich_Barton" "etzioni" "hadip" "RajSinghSeattle"
## [13] "funcOfJoe" "Kristen_Hammy"
##
## + ... omitted several groups/vertices
Veamos la modularidad obtenida:
modularity(topsNet_giant_LV)
## [1] 0.1848126
Y las comunidades:
communities(topsNet_giant_LV)
## $`1`
## [1] "rachelerman" "gilbert" "mattmday" "daryn" "danshapiro"
## [6] "medinism" "KieranSnyder" "stevesi" "JenMsft"
##
## $`2`
## [1] "mattmcilwain" "toddbishop" "akipman" "matt_oppy"
## [5] "juliesandler" "BradSmi" "crashdev" "john_gabbert"
## [9] "Rich_Barton" "etzioni" "hadip" "RajSinghSeattle"
## [13] "funcOfJoe" "Kristen_Hammy"
##
## $`3`
## [1] "DaveParkerSEA" "ShaunaCausey" "moniguzman" "MissDestructo"
## [5] "Ryanintheus" "SoGulley" "jinman" "Jenerationy"
## [9] "lanctot" "nhuntwalker" "PeterHamilton" "sarahstood"
## [13] "marybethlambert"
##
## $`4`
## [1] "ashannstew" "LeslieFeinzaig" "lovelletters" "heatherredman"
## [5] "kirbywinfield" "sonalpmane" "mcolacurcio"
La pertenecia a cada grupo podemos guardarla nuevamente como un atributo del nodo:
V(topsNet_giant)$LVpartition=topsNet_giant_LV$membership
Aqui de manera gráfica:
l=layout_with_kk(topsNet_giant)
plot(topsNet_giant_LV,topsNet_giant,layout=l,vertex.label='',edge.arrow.size=.2)
Con el grafo actual (topsNet_giant.graphml), calculemos comunidades con la función Modularidad de Gephi (usa la técnica Louvain) . Notemos las diferencias (particiones, modularidad, membresía).
Un tema clave es encontrar, en medio de todos los nodos, aquellos cuya posición y patrón de conexiones nos revele que tienen mejores condiciones para facilitar o restringir el flujo que se traslada por la red. Nótese en la Figura 7 la presencia de agujeros, y la emergencia de nodos que median el flujo entre subredes.
Por ejemplo, podemos calcular los tres nodos más claves siguiendo esa idea:
#install.packages("influenceR", dependencies = TRUE)
influenceR::keyplayer(topsNet_giant,3)
## + 3/43 vertices, named, from 9b4f299:
## [1] DaveParkerSEA akipman ShaunaCausey
Alternativamente, podemos revelar quiénes tienen menos restricciones en la red para lo mismo:facilitar o restringir el flujo que se traslada por la red.
sort(igraph::constraint(topsNet_giant))
## toddbishop ShaunaCausey stevesi DaveParkerSEA juliesandler
## 0.1013494 0.1123185 0.1135172 0.1145126 0.1156528
## rachelerman heatherredman hadip moniguzman crashdev
## 0.1159127 0.1182542 0.1194759 0.1195957 0.1227078
## lovelletters KieranSnyder MissDestructo SoGulley matt_oppy
## 0.1258692 0.1278971 0.1282831 0.1287263 0.1314037
## RajSinghSeattle Rich_Barton etzioni gilbert ashannstew
## 0.1334484 0.1371157 0.1381820 0.1393513 0.1394147
## kirbywinfield LeslieFeinzaig Ryanintheus mattmcilwain Jenerationy
## 0.1414997 0.1430804 0.1431305 0.1433083 0.1454532
## mattmday danshapiro BradSmi Kristen_Hammy lanctot
## 0.1487804 0.1493675 0.1502496 0.1506816 0.1622389
## sonalpmane mcolacurcio daryn PeterHamilton medinism
## 0.1661130 0.1691871 0.1775320 0.1784957 0.1853486
## funcOfJoe sarahstood JenMsft john_gabbert nhuntwalker
## 0.1887772 0.2040247 0.2212805 0.2234507 0.2307269
## jinman akipman marybethlambert
## 0.2464649 0.2765752 0.2861118
Así, sabiendo que hay agujeros estructurales, podemos animarnos a asignar roles de brokerage.
Pensemos en una red que tiene tres particiones A, B, y C, es decir, los nodos están en sólo una de ellas, podemos asignar los siguientes roles:
El Coordinador: Al que media la comunicacíon dentro de su propia partición, es decir, el coordinador aCD se ubica así: ai->aCD->aj, ai,aCD,aj pertenecen a A.
El Consultor: Al que media la comunicacíon dentro de otra partición, es decir, el consultor bCT se ubica así: ai->bCT->aj, ai,aj pertenecen a A, bCT pertenece a B.
El Portero: Al que permite que una partición ajena se comunique con su propia partición, es decir, el portero bPT se ubica así: ai->bPT->bj, ai pertenece a A, y bPT, y bj pertenecen a B.
El Representante: Al que se acerca a otra partición con información de su propia partición, es decir, el representante aRP se ubica así: ai->aRP->bj, ai, y aRP pertenecen a A, y bi pertenecen a B.
El Enlace (liasion): Al que media entre particiones diferentes, es decir, el liason aLI se ubica así: bi->aLI->cj, bi pertenece a B, cj pertenece a C, aLI pertenece a A.
La librería igraph no calcula estos roles, pero si se puede con la librería statnet. Primero, se debe convertir de nuevo la red de un formato a otro para actualizarla con loos cálculos recientes:
topsNet_giant_statnet <- intergraph::asNetwork(topsNet_giant)
Ahora, veamos los roles:
library(statnet)
brokerage(topsNet_giant_statnet,
cl=get.vertex.attribute(topsNet_giant_statnet, "LVpartition"))$raw.nli
## w_I w_O b_IO b_OI b_O t
## rachelerman 14 31 78 69 123 315
## mattmcilwain 13 2 11 13 8 47
## DaveParkerSEA 26 11 90 52 56 235
## toddbishop 57 81 169 190 229 726
## ashannstew 0 4 14 7 23 48
## LeslieFeinzaig 12 6 26 29 23 96
## akipman 0 0 0 1 0 1
## matt_oppy 11 3 42 16 25 97
## gilbert 5 1 4 24 9 43
## juliesandler 35 17 72 116 93 333
## BradSmi 15 2 8 17 5 47
## crashdev 23 8 26 58 33 148
## ShaunaCausey 62 22 114 96 65 359
## john_gabbert 0 0 1 1 0 2
## moniguzman 28 17 87 53 53 238
## mattmday 7 2 19 11 14 53
## Rich_Barton 8 2 5 7 0 22
## daryn 4 0 8 4 0 16
## lovelletters 8 19 57 32 84 200
## etzioni 20 2 28 22 12 84
## MissDestructo 12 6 39 34 42 133
## heatherredman 12 37 69 58 118 294
## danshapiro 4 0 1 10 2 17
## medinism 0 0 1 5 0 6
## KieranSnyder 11 6 29 25 27 98
## hadip 8 4 8 29 13 62
## RajSinghSeattle 13 1 6 26 2 48
## funcOfJoe 1 0 2 1 0 4
## kirbywinfield 0 5 11 11 29 56
## stevesi 8 23 48 52 97 228
## Ryanintheus 18 0 23 9 2 52
## sonalpmane 0 1 7 0 7 15
## SoGulley 9 0 24 10 8 51
## jinman 0 0 1 0 0 1
## Jenerationy 7 1 20 1 0 29
## lanctot 0 1 7 2 3 13
## Kristen_Hammy 1 0 2 14 7 24
## nhuntwalker 2 1 4 3 1 11
## JenMsft 0 0 2 3 4 9
## PeterHamilton 2 0 5 4 2 13
## sarahstood 2 0 6 0 0 8
## mcolacurcio 0 1 8 2 1 12
## marybethlambert 0 0 0 1 0 1
Un poco de traducción:
El Coordinador: w_I
El Consultor: w_O
El Portero: b_IO
El Representante:b_OI
El Enlace (liasion): b_O
Es decir: rachelerman cada uno de esos roles la cantidad indicada en cada columna.
Creemos el archivo de enlaces (edgelist) desde R:
Puede descargar ese archivo desde la carpeta de datos en este momento.
Luego, desde Gephi, vayamos al Laboratorio de datos y descarguemos en CSV los atributos calculados por nodo, llame a ese archivo Seattle_giant_nodos.csv.
Antes de crearlo, cerciorese de sólo seleccionar dos columnas, la que tiene el nombre, y la que tiene la partición por modularidad (Louvain). Sobretodo, evite guardar ID y LABEL, tal como se indica en la Figura 9.
Ahora abrimos UCINET, y abrimos estos archivos desde el DL Editor:
Para calcular el constraint de los agujeros estructurales, sigamos los pasos indicados en las Figuras 12 y 13.
Para calcular los roles de brokerage, sigamos los pasos indicados en las Figuras 14 y 15.
Recuerda que el brokerage se ha obtenido usando las
particiones que te da Gephi, no las de R.
Para este ejercicio trabaje con toda la red de los innovadores de Seattle (no sólo con el mayor componente).
Prepara tu respuesta con texto e imagenes de lo obtenido en un GoogleDoc.